/*
 * FreeRangeSpot.java
 */

package org.sunspotworld.demo;

import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.peripheral.TimeoutException;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.util.BootloaderListener;
import com.sun.spot.util.Utils;
import java.io.IOException;
import java.util.Random;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/**
 * @author mi zhang
 */
public class FreeRangeSpot extends MIDlet implements Runnable, ProtocolParameter, PacketTypes {

    /**
     * Sensors Needed.
     */

    /**
     * Local Variables.
     */
    //
    private Random rand = new Random();
    // My assigned port
    private int myPort = -1;
    // Loop Control
    private boolean broadcastDo = false;
    private Receiver myReceiver = null;

    /**
     * Pause for a specified time.
     *
     * @param time the number of milliseconds to pause
     */
    public void pause (long time) {
        try {
            Thread.currentThread().sleep(time);
        } catch (InterruptedException ex) { System.err.println("Caught " + ex + " while pausing."); }
    }

    public void flashstatusLED(LEDColor myColor) {
        statusLED.setColor(myColor);
        statusLED.setOn();
        pause(50);
        statusLED.setOff();
    }

    public void displaySignalStrength (int signalStrength) {

     // Turn off the LEDs before each update
        signalStrengthLED1.setOff();
        signalStrengthLED2.setOff();
        signalStrengthLED3.setOff();
        signalStrengthLED4.setOff();
        signalStrengthLED5.setOff();
        signalStrengthLED6.setOff();

        if (signalStrength > 0)
            signalStrengthLED1.setOn();
        if (signalStrength > 20)
            signalStrengthLED2.setOn();
        if (signalStrength > 40)
            signalStrengthLED3.setOn();
        if (signalStrength > 60)
            signalStrengthLED4.setOn();
        if (signalStrength > 80)
            signalStrengthLED5.setOn();
        if (signalStrength > 100)
            signalStrengthLED6.setOn();
    }

    /**
     * Initialize any needed variables.
     */
    public void initialize() {

        statusLED.setColor(red);     // Red = not active
        statusLED.setOn();
     // Turn off the signal strength LEDs
        signalStrengthLED1.setColor(blue);
        signalStrengthLED1.setOff();
        signalStrengthLED2.setColor(blue);
        signalStrengthLED2.setOff();
        signalStrengthLED3.setColor(blue);
        signalStrengthLED3.setOff();
        signalStrengthLED4.setColor(blue);
        signalStrengthLED4.setOff();
        signalStrengthLED5.setColor(blue);
        signalStrengthLED5.setOff();
        signalStrengthLED6.setColor(blue);
        signalStrengthLED6.setOff();
     // Initialize the radio
        IRadioPolicyManager rpm = Spot.getInstance().getRadioPolicyManager();
        rpm.setChannelNumber(channel);
        rpm.setPanId(PAN_ID);
        rpm.setOutputPower(power - 32); // "power = 0" is the maximum power allowed for SPOT
        myPort = -1;
     // Start broadcasting Heartbeat
        broadcastDo = true;
    }

    /**
     * "Heartbeat" Packets Broadcaster
     * NOTE: TIMEOUT is needed since FR should preserve power as much as possible.
     */
    public void broadcastHeartbeatLoop () {
        System.out.println("Thread_BHBL: Begins.");
        RadiogramConnection txConn = null;
        RadiogramConnection rcvConn = null;
        Datagram xdg = null;
        Radiogram rdg = null;
        long now = 0;
        String ourAddress = System.getProperty("IEEE_ADDRESS");
        System.out.println("Starting Heartbeat Broadcast server on " +
                ourAddress + " ...");
        int debug_counter = 0;
        try {
        /**
         * Step1: Open a Broadcast connection to broadcast the Heartbeat packets, and
         * open a receiver start receiving the ACK.
         */
            txConn = (RadiogramConnection) Connector.open("radiogram://broadcast:" + HEARTBEAT_BROADCAST_PORT); // Broadcast !
            txConn.setMaxBroadcastHops(1);
            xdg = (Radiogram)txConn.newDatagram(txConn.getMaximumLength());
            rcvConn = (RadiogramConnection)Connector.open("radiogram://:" + HEARTBEAT_BROADCAST_PORT); // Server & Full-Duplex!
            rcvConn.setTimeout(TIMEOUT_PERIOD);
            rdg = (Radiogram)rcvConn.newDatagram(rcvConn.getMaximumLength());
        } catch (IOException ex) {
            System.err.println("Thread_BHBL: Caught " + ex + " in txConn/rcvConn connection initialization in FR");
            ex.printStackTrace();
            //System.exit(1);
        }
        /**
         * Step2: Broadcast the Heartbeat packets.
         */
        while (broadcastDo) {
            try {
                debug_counter++;
                System.out.println("Thread_BHBL: " + debug_counter + " starts.");
                now = System.currentTimeMillis();
                System.out.println("Thread_BHBL: " + debug_counter + " now = " + now);
             // Package the HEARTBEAT datagram and send it.
                xdg.reset();
                xdg.writeByte(HEARTBEAT_PACKET);
             // After RANDOM millis, send the broadcast packet -- reduce possibility of collisions
                Utils.sleep(rand.nextInt(MAX_SLEEP_TIME));
                flashstatusLED(green);                  // Green = packet sent
                txConn.send(xdg);
                System.out.println("Thread_BHBL: " + debug_counter + " Broadcast packet sent.");
                rdg.reset();
                rcvConn.receive(rdg);                   // Blocking
                final String basestationAddress = rdg.getAddress();     // read sender's Id
                byte packetType = rdg.readByte();
                if (packetType == ACK_FOR_HEARTBEAT_PACKET) {
                    System.out.println("Thread_BHBL: " + debug_counter + " is ACK_FOR_HEARTBEAT_PACKET.");
                    flashstatusLED(blue);               // Blue = receive ACK_FOR_HEARTBEAT_PACKET packet
                    int signalStrength = rdg.getRssi() + 60;
                    System.out.println("Thread_BHBL: Signal Strength is: " + signalStrength);
                    displaySignalStrength(signalStrength);
                 // a) Get my Port Number
                    final int myAssignedPort = rdg.readInt();
                 // b) Close the connector first
                     txConn.close();
                     rcvConn.close();
                 // c) Spawn a Receiver with the new port for the first time I recieve the ASSIGNPORT_PACKET packet.
                    if (myAssignedPort != -1){
                        myPort = myAssignedPort;
                        System.out.println("Thread_BHBL: My port is: " + myPort);
                        myReceiver = new Receiver(myPort, basestationAddress);
                        new Thread() {
                            public void run () {
                                myReceiver.receiverRun();
                            }
                        }.start();
                 // d) Go to Die!!!
                        broadcastDo = false;   
                        break;
                    }      
                } else {
                    /* Ignore other kinds of packets */
                    System.out.println("Thread_BHBL: " + debug_counter + " OTHER PACKET!");
                }
            } catch (TimeoutException tex) {        // Timeout - display no packet received
                    System.out.println("Thread_BHBL: " + debug_counter + " Timeout.");
                    statusLED.setColor(red);        // Red = Timeout
                    statusLED.setOn();
                 // Turn off the LEDs before each update
                    signalStrengthLED1.setOff();
                    signalStrengthLED2.setOff();
                    signalStrengthLED3.setOff();
                    signalStrengthLED4.setOff();
                    signalStrengthLED5.setOff();
                    signalStrengthLED6.setOff();
                 // Go to sleep to save power
                    if (HEARTBEAT_PERIOD - (System.currentTimeMillis() - now) >= 0) {
                        System.out.println("Thread_BHBL: Go to sleep.");
                        Utils.sleep(HEARTBEAT_PERIOD - (System.currentTimeMillis() - now));
                    }
            } catch (Exception ex) {
                System.err.println("Thread_BHBL: Caught " + ex + " while broadcasting Heartbeat packets in FR.");
                ex.printStackTrace();
            }
        }
    }

    /**
     * Main application run loop.
     */
    public void run() {

        System.out.println("Welcome to the Free Range SPOT Application!");
        broadcastHeartbeatLoop();
        System.out.println("FreeRangeSPOT dies.");
    }

    protected void startApp() throws MIDletStateChangeException {

        new BootloaderListener().start();   // monitor the USB (if connected) and recognize commands from host
        System.out.println("FreeRanegSpot starts.");
        initialize();
        Thread t = new Thread(this);
        t.start();
    }


    protected void pauseApp() {
        // This is not currently called by the Squawk VM
    }

    /**
     * Called if the MIDlet is terminated by the system.
     * I.e. if startApp throws any exception other than MIDletStateChangeException,
     * if the isolate running the MIDlet is killed with Isolate.exit(), or
     * if VM.stopVM() is called.
     *
     * It is not called if MIDlet.notifyDestroyed() was called.
     *
     * @param unconditional If true when this method is called, the MIDlet must
     *    cleanup and release all resources. If false the MIDlet may throw
     *    MIDletStateChangeException  to indicate it does not want to be destroyed
     *    at this time.
     */
    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
        for (int i = 0; i < 8; i++) {
            leds[i].setOff();
        }
    }
}
